;(function (window, $, undefined) {
	
	window.Datepicker = '';

	(function () {
		var pluginName = 'datepicker',
			autoInitSelector = '.datepicker-here',
			$body, $datepickersContainer,
			containerBuilt = false,
			baseTemplate = '' +
				'<div class="datepicker">' +
				'<i class="datepicker--pointer"></i>' +
				'<nav class="datepicker--nav"></nav>' +
				'<div class="datepicker--content"></div>' +
				'</div>',
			defaults = {
				classes: '',
				inline: false,
				language: 'ru',
				startDate: new Date(),
				firstDay: '',
				weekends: [6, 0],
				dateFormat: '',
				altField: '',
				altFieldDateFormat: '@',
				toggleSelected: true,
				keyboardNav: true,
	
				position: 'bottom left',
				offset: 12,
	
				view: 'days',
				minView: 'days',
	
				showOtherMonths: true,
				selectOtherMonths: true,
				moveToOtherMonthsOnSelect: true,
	
				showOtherYears: true,
				selectOtherYears: true,
				moveToOtherYearsOnSelect: true,
	
				minDate: '',
				maxDate: '',
				disableNavWhenOutOfRange: true,
	
				multipleDates: false, // Boolean or Number
				multipleDatesSeparator: ',',
				range: false,
	
				todayButton: false,
				clearButton: false,
	
				showEvent: 'focus',
				autoClose: false,
	
				// navigation
				monthsField: 'monthsShort',
				prevHtml: '<svg><path d="M 17,12 l -5,5 l 5,5"></path></svg>',
				nextHtml: '<svg><path d="M 14,12 l 5,5 l -5,5"></path></svg>',
				navTitles: {
					days: 'MM, <i>yyyy</i>',
					months: 'yyyy',
					years: 'yyyy1 - yyyy2'
				},
	
				// events
				onSelect: '',
				onChangeMonth: '',
				onChangeYear: '',
				onChangeDecade: '',
				onChangeView: '',
				onRenderCell: ''
			},
			hotKeys = {
				'ctrlRight': [17, 39],
				'ctrlUp': [17, 38],
				'ctrlLeft': [17, 37],
				'ctrlDown': [17, 40],
				'shiftRight': [16, 39],
				'shiftUp': [16, 38],
				'shiftLeft': [16, 37],
				'shiftDown': [16, 40],
				'altUp': [18, 38],
				'altRight': [18, 39],
				'altLeft': [18, 37],
				'altDown': [18, 40],
				'ctrlShiftUp': [16, 17, 38]
			},
			datepicker;
	
		Datepicker  = function (el, options) {
			this.el = el;
			this.$el = $(el);
	
			this.opts = $.extend(true, {}, defaults, options, this.$el.data());
	
			if ($body == undefined) {
				$body = $('body');
			}
	
			if (!this.opts.startDate) {
				this.opts.startDate = new Date();
			}
	
			if (this.el.nodeName == 'INPUT') {
				this.elIsInput = true;
			}
	
			if (this.opts.altField) {
				this.$altField = typeof this.opts.altField == 'string' ? $(this.opts.altField) : this.opts.altField;
			}
	
			this.inited = false;
			this.visible = false;
			this.silent = false; // Need to prevent unnecessary rendering
	
			this.currentDate = this.opts.startDate;
			this.currentView = this.opts.view;
			this._createShortCuts();
			this.selectedDates = [];
			this.views = {};
			this.keys = [];
			this.minRange = '';
			this.maxRange = '';
	
			this.init()
		};
	
		datepicker = Datepicker;
	
		datepicker.prototype = {
			viewIndexes: ['days', 'months', 'years'],
	
			init: function () {
				if (!containerBuilt && !this.opts.inline && this.elIsInput) {
					this._buildDatepickersContainer();
				}
				this._buildBaseHtml();
				this._defineLocale(this.opts.language);
				this._syncWithMinMaxDates();
	
				if (this.elIsInput) {
					if (!this.opts.inline) {
						// Set extra classes for proper transitions
						this._setPositionClasses(this.opts.position);
						this._bindEvents()
					}
					if (this.opts.keyboardNav) {
						this._bindKeyboardEvents();
					}
					this.$datepicker.on('mousedown', this._onMouseDownDatepicker.bind(this));
					this.$datepicker.on('mouseup', this._onMouseUpDatepicker.bind(this));
				}
	
				if (this.opts.classes) {
					this.$datepicker.addClass(this.opts.classes)
				}
	
				this.views[this.currentView] = new Datepicker.Body(this, this.currentView, this.opts);
				this.views[this.currentView].show();
				this.nav = new Datepicker.Navigation(this, this.opts);
				this.view = this.currentView;
	
				this.$datepicker.on('mouseenter', '.datepicker--cell', this._onMouseEnterCell.bind(this));
				this.$datepicker.on('mouseleave', '.datepicker--cell', this._onMouseLeaveCell.bind(this));
	
				this.inited = true;
			},
	
			_createShortCuts: function () {
				this.minDate = this.opts.minDate ? this.opts.minDate : new Date(-8639999913600000);
				this.maxDate = this.opts.maxDate ? this.opts.maxDate : new Date(8639999913600000);
			},
	
			_bindEvents : function () {
				this.$el.on(this.opts.showEvent + '.adp', this._onShowEvent.bind(this));
				this.$el.on('blur.adp', this._onBlur.bind(this));
				this.$el.on('input.adp', this._onInput.bind(this));
				$(window).on('resize.adp', this._onResize.bind(this));
			},
	
			_bindKeyboardEvents: function () {
				this.$el.on('keydown.adp', this._onKeyDown.bind(this));
				this.$el.on('keyup.adp', this._onKeyUp.bind(this));
				this.$el.on('hotKey.adp', this._onHotKey.bind(this));
			},
	
			isWeekend: function (day) {
				return this.opts.weekends.indexOf(day) !== -1;
			},
	
			_defineLocale: function (lang) {
				if (typeof lang == 'string') {
					this.loc = Datepicker.language[lang];
					if (!this.loc) {
						console.warn('Can\'t find language "' + lang + '" in Datepicker.language, will use "ru" instead');
						this.loc = $.extend(true, {}, Datepicker.language.ru)
					}
	
					this.loc = $.extend(true, {}, Datepicker.language.ru, Datepicker.language[lang])
				} else {
					this.loc = $.extend(true, {}, Datepicker.language.ru, lang)
				}
	
				if (this.opts.dateFormat) {
					this.loc.dateFormat = this.opts.dateFormat
				}
	
				if (this.opts.firstDay !== '') {
					this.loc.firstDay = this.opts.firstDay
				}
			},
	
			_buildDatepickersContainer: function () {
				containerBuilt = true;
				$body.append('<div class="datepickers-container" id="datepickers-container"></div>');
				$datepickersContainer = $('#datepickers-container');
			},
	
			_buildBaseHtml: function () {
				var $appendTarget,
					$inline = $('<div class="datepicker-inline">');
	
				if(this.el.nodeName == 'INPUT') {
					if (!this.opts.inline) {
						$appendTarget = $datepickersContainer;
					} else {
						$appendTarget = $inline.insertAfter(this.$el)
					}
				} else {
					$appendTarget = $inline.appendTo(this.$el)
				}
	
				this.$datepicker = $(baseTemplate).appendTo($appendTarget);
				this.$content = $('.datepicker--content', this.$datepicker);
				this.$nav = $('.datepicker--nav', this.$datepicker);
			},
	
			_triggerOnChange: function () {
				if (!this.selectedDates.length) {
					return this.opts.onSelect('', '', this);
				}
	
				var selectedDates = this.selectedDates,
					parsedSelected = datepicker.getParsedDate(selectedDates[0]),
					formattedDates,
					_this = this,
					dates = new Date(parsedSelected.year, parsedSelected.month, parsedSelected.date);
	
					formattedDates = selectedDates.map(function (date) {
						return _this.formatDate(_this.loc.dateFormat, date)
					}).join(this.opts.multipleDatesSeparator);
	
				// Create new dates array, to separate it from original selectedDates
				if (this.opts.multipleDates || this.opts.range) {
					dates = selectedDates.map(function(date) {
						var parsedDate = datepicker.getParsedDate(date);
						return new Date(parsedDate.year, parsedDate.month, parsedDate.date)
					})
				}
	
				this.opts.onSelect(formattedDates, dates, this);
			},
	
			next: function () {
				var d = this.parsedDate,
					o = this.opts;
				switch (this.view) {
					case 'days':
						this.date = new Date(d.year, d.month + 1, 1);
						if (o.onChangeMonth) o.onChangeMonth(this.parsedDate.month, this.parsedDate.year);
						break;
					case 'months':
						this.date = new Date(d.year + 1, d.month, 1);
						if (o.onChangeYear) o.onChangeYear(this.parsedDate.year);
						break;
					case 'years':
						this.date = new Date(d.year + 10, 0, 1);
						if (o.onChangeDecade) o.onChangeDecade(this.curDecade);
						break;
				}
			},
	
			prev: function () {
				var d = this.parsedDate,
					o = this.opts;
				switch (this.view) {
					case 'days':
						this.date = new Date(d.year, d.month - 1, 1);
						if (o.onChangeMonth) o.onChangeMonth(this.parsedDate.month, this.parsedDate.year);
						break;
					case 'months':
						this.date = new Date(d.year - 1, d.month, 1);
						if (o.onChangeYear) o.onChangeYear(this.parsedDate.year);
						break;
					case 'years':
						this.date = new Date(d.year - 10, 0, 1);
						if (o.onChangeDecade) o.onChangeDecade(this.curDecade);
						break;
				}
			},
	
			formatDate: function (string, date) {
				date = date || this.date;
				var result = string,
					boundary = this._getWordBoundaryRegExp,
					locale = this.loc,
					decade = datepicker.getDecade(date),
					d = datepicker.getParsedDate(date);
	
				switch (true) {
					case /@/.test(result):
						result = result.replace(/@/, date.getTime());
					case /dd/.test(result):
						result = result.replace(boundary('dd'), d.fullDate);
					case /d/.test(result):
						result = result.replace(boundary('d'), d.date);
					case /DD/.test(result):
						result = result.replace(boundary('DD'), locale.days[d.day]);
					case /D/.test(result):
						result = result.replace(boundary('D'), locale.daysShort[d.day]);
					case /mm/.test(result):
						result = result.replace(boundary('mm'), d.fullMonth);
					case /m/.test(result):
						result = result.replace(boundary('m'), d.month + 1);
					case /MM/.test(result):
						result = result.replace(boundary('MM'), this.loc.months[d.month]);
					case /M/.test(result):
						result = result.replace(boundary('M'), locale.monthsShort[d.month]);
					case /yyyy/.test(result):
						result = result.replace(boundary('yyyy'), d.year);
					case /yyyy1/.test(result):
						result = result.replace(boundary('yyyy1'), decade[0]);
					case /yyyy2/.test(result):
						result = result.replace(boundary('yyyy2'), decade[1]);
					case /yy/.test(result):
						result = result.replace(boundary('yy'), d.year.toString().slice(-2));
				}
	
				return result;
			},
	
			_getWordBoundaryRegExp: function (sign) {
				return new RegExp('\\b(?=[a-zA-Z0-9äöüßÄÖÜ<])' + sign + '(?![>a-zA-Z0-9äöüßÄÖÜ])');
			},
	
			selectDate: function (date) {
				var _this = this,
					opts = _this.opts,
					d = _this.parsedDate,
					selectedDates = _this.selectedDates,
					len = selectedDates.length,
					newDate = '';
	
				if (!(date instanceof Date)) return;
	
				if (_this.view == 'days') {
					if (date.getMonth() != d.month && opts.moveToOtherMonthsOnSelect) {
						newDate = new Date(date.getFullYear(), date.getMonth(), 1);
					}
				}
	
				if (_this.view == 'years') {
					if (date.getFullYear() != d.year && opts.moveToOtherYearsOnSelect) {
						newDate = new Date(date.getFullYear(), 0, 1);
					}
				}
	
				if (newDate) {
					_this.silent = true;
					_this.date = newDate;
					_this.silent = false;
					_this.nav._render();
				}
	
				if (opts.multipleDates && !opts.range) { // Set priority to range functionality
					if (len === opts.multipleDates) return;
					if (!_this._isSelected(date)) {
						_this.selectedDates.push(date);
					}
				} else if (opts.range) {
					if (len == 2) {
						_this.selectedDates = [date];
						_this.minRange = date;
						_this.maxRange = '';
					} else if (len == 1) {
						_this.selectedDates.push(date);
						if (!_this.maxRange){
							_this.maxRange = date;
						} else {
							_this.minRange = date;
						}
						_this.selectedDates = [_this.minRange, _this.maxRange]
	
					} else {
						_this.selectedDates = [date];
						_this.minRange = date;
					}
				} else {
					_this.selectedDates = [date];
				}
	
				_this._setInputValue();
	
				if (opts.onSelect) {
					_this._triggerOnChange();
				}
	
				if (opts.autoClose) {
					if (!opts.multipleDates && !opts.range) {
						_this.hide();
					} else if (opts.range && _this.selectedDates.length == 2) {
						_this.hide();
					}
				}
	
				_this.views[this.currentView]._render()
			},
	
			removeDate: function (date) {
				var selected = this.selectedDates,
					_this = this;
	
				if (!(date instanceof Date)) return;
	
				return selected.some(function (curDate, i) {
					if (datepicker.isSame(curDate, date)) {
						selected.splice(i, 1);
	
						if (!_this.selectedDates.length) {
							_this.minRange = '';
							_this.maxRange = '';
						}
	
						_this.views[_this.currentView]._render();
						_this._setInputValue();
	
						if (_this.opts.onSelect) {
							_this._triggerOnChange();
						}
	
						return true;
					}
				})
			},
	
			today: function () {
				this.silent = true;
				this.view = this.opts.minView;
				this.silent = false;
				this.date = new Date();
			},
	
			clear: function () {
				this.selectedDates = [];
				this.minRange = '';
				this.maxRange = '';
				this.views[this.currentView]._render();
				this._setInputValue();
				if (this.opts.onSelect) {
					this._triggerOnChange()
				}
			},
	
			/**
			 * Updates datepicker options
			 * @param {String|Object} param - parameter's name to update. If object then it will extend current options
			 * @param {String|Number|Object} [value] - new param value
			 */
			update: function (param, value) {
				var len = arguments.length;
				if (len == 2) {
					this.opts[param] = value;
				} else if (len == 1 && typeof param == 'object') {
					this.opts = $.extend(true, this.opts, param)
				}
	
				this._createShortCuts();
				this._syncWithMinMaxDates();
				this._defineLocale(this.opts.language);
				this.nav._addButtonsIfNeed();
				this.nav._render();
				this.views[this.currentView]._render();
	
				if (this.elIsInput && !this.opts.inline) {
					this._setPositionClasses(this.opts.position);
					if (this.visible) {
						this.setPosition(this.opts.position)
					}
				}
	
				if (this.opts.classes) {
					this.$datepicker.addClass(this.opts.classes)
				}
	
				return this;
			},
	
			_syncWithMinMaxDates: function () {
				var curTime = this.date.getTime();
				this.silent = true;
				if (this.minTime > curTime) {
					this.date = this.minDate;
				}
	
				if (this.maxTime < curTime) {
					this.date = this.maxDate;
				}
				this.silent = false;
			},
	
			_isSelected: function (checkDate, cellType) {
				return this.selectedDates.some(function (date) {
					return datepicker.isSame(date, checkDate, cellType)
				})
			},
	
			_setInputValue: function () {
				var _this = this,
					opts = _this.opts,
					format = _this.loc.dateFormat,
					altFormat = opts.altFieldDateFormat,
					value = _this.selectedDates.map(function (date) {
						return _this.formatDate(format, date)
					}),
					altValues;
	
				if (opts.altField && _this.$altField.length) {
					altValues = this.selectedDates.map(function (date) {
						return _this.formatDate(altFormat, date)
					});
					altValues = altValues.join(this.opts.multipleDatesSeparator);
					this.$altField.val(altValues);
				}
	
				value = value.join(this.opts.multipleDatesSeparator);
	
				this.$el.val(value);
				
				this.hide();
			},
	
			/**
			 * Check if date is between minDate and maxDate
			 * @param date {object} - date object
			 * @param type {string} - cell type
			 * @returns {boolean}
			 * @private
			 */
			_isInRange: function (date, type) {
				var time = date.getTime(),
					d = datepicker.getParsedDate(date),
					min = datepicker.getParsedDate(this.minDate),
					max = datepicker.getParsedDate(this.maxDate),
					dMinTime = new Date(d.year, d.month, min.date).getTime(),
					dMaxTime = new Date(d.year, d.month, max.date).getTime(),
					types = {
						day: time >= this.minTime && time <= this.maxTime,
						month: dMinTime >= this.minTime && dMaxTime <= this.maxTime,
						year: d.year >= min.year && d.year <= max.year
					};
				return type ? types[type] : types.day
			},
	
			_getDimensions: function ($el) {
				var offset = $el.offset();
	
				return {
					width: $el.outerWidth(),
					height: $el.outerHeight(),
					left: offset.left,
					top: offset.top
				}
			},
	
			_getDateFromCell: function (cell) {
				var curDate = this.parsedDate,
					year = cell.data('year') || curDate.year,
					month = cell.data('month') == undefined ? curDate.month : cell.data('month'),
					date = cell.data('date') || 1;
	
				return new Date(year, month, date);
			},
	
			_setPositionClasses: function (pos) {
				pos = pos.split(' ');
				var main = pos[0],
					sec = pos[1],
					classes = 'datepicker -' + main + '-' + sec + '- -from-' + main + '-';
	
				if (this.visible) classes += ' active';
	
				this.$datepicker
					.removeAttr('class')
					.addClass(classes);
			},
	
			setPosition: function (position) {
				position = position || this.opts.position;
	
				var dims = this._getDimensions(this.$el),
					selfDims = this._getDimensions(this.$datepicker),
					pos = position.split(' '),
					top, left,
					offset = this.opts.offset,
					main = pos[0],
					secondary = pos[1];
	
				switch (main) {
					case 'top':
						top = dims.top - selfDims.height - offset;
						break;
					case 'right':
						left = dims.left + dims.width + offset;
						break;
					case 'bottom':
						top = dims.top + dims.height + offset;
						break;
					case 'left':
						left = dims.left - selfDims.width - offset;
						break;
				}
	
				switch(secondary) {
					case 'top':
						top = dims.top;
						break;
					case 'right':
						left = dims.left + dims.width - selfDims.width;
						break;
					case 'bottom':
						top = dims.top + dims.height - selfDims.height;
						break;
					case 'left':
						left = dims.left;
						break;
					case 'center':
						if (/left|right/.test(main)) {
							top = dims.top + dims.height/2 - selfDims.height/2;
						} else {
							left = dims.left + dims.width/2 - selfDims.width/2;
						}
				}
	
				this.$datepicker
					.css({
						left: left,
						top: top
					})
			},
	
			show: function () {
				this.setPosition(this.opts.position);
				this.$datepicker.addClass('active');
				this.visible = true;
			},
	
			hide: function () {
				this.$datepicker
					.removeClass('active')
					.css({
						left: '-100000px'
					});
	
				this.focused = '';
				this.keys = [];
	
				this.inFocus = false;
				this.visible = false;
				this.$el.blur();
			},
	
			down: function (date) {
				this._changeView(date, 'down');
			},
	
			up: function (date) {
				this._changeView(date, 'up');
			},
	
			_changeView: function (date, dir) {
				date = date || this.focused || this.date;
	
				var nextView = dir == 'up' ? this.viewIndex + 1 : this.viewIndex - 1;
				if (nextView > 2) nextView = 2;
				if (nextView < 0) nextView = 0;
	
				this.silent = true;
				this.date = new Date(date.getFullYear(), date.getMonth(), 1);
				this.silent = false;
				this.view = this.viewIndexes[nextView];
	
			},
	
			_handleHotKey: function (key) {
				var date = datepicker.getParsedDate(this._getFocusedDate()),
					focusedParsed,
					o = this.opts,
					newDate,
					totalDaysInNextMonth,
					monthChanged = false,
					yearChanged = false,
					decadeChanged = false,
					y = date.year,
					m = date.month,
					d = date.date;
	
				switch (key) {
					case 'ctrlRight':
					case 'ctrlUp':
						m += 1;
						monthChanged = true;
						break;
					case 'ctrlLeft':
					case 'ctrlDown':
						m -= 1;
						monthChanged = true;
						break;
					case 'shiftRight':
					case 'shiftUp':
						yearChanged = true;
						y += 1;
						break;
					case 'shiftLeft':
					case 'shiftDown':
						yearChanged = true;
						y -= 1;
						break;
					case 'altRight':
					case 'altUp':
						decadeChanged = true;
						y += 10;
						break;
					case 'altLeft':
					case 'altDown':
						decadeChanged = true;
						y -= 10;
						break;
					case 'ctrlShiftUp':
						this.up();
						break;
				}
	
				totalDaysInNextMonth = datepicker.getDaysCount(new Date(y,m));
				newDate = new Date(y,m,d);
	
				// If next month has less days than current, set date to total days in that month
				if (totalDaysInNextMonth < d) d = totalDaysInNextMonth;
	
				// Check if newDate is in valid range
				if (newDate.getTime() < this.minTime) {
					newDate = this.minDate;
				} else if (newDate.getTime() > this.maxTime) {
					newDate = this.maxDate;
				}
	
				this.focused = newDate;
	
				focusedParsed = datepicker.getParsedDate(newDate);
				if (monthChanged && o.onChangeMonth) {
					o.onChangeMonth(focusedParsed.month, focusedParsed.year)
				}
				if (yearChanged && o.onChangeYear) {
					o.onChangeYear(focusedParsed.year)
				}
				if (decadeChanged && o.onChangeDecade) {
					o.onChangeDecade(this.curDecade)
				}
			},
	
			_registerKey: function (key) {
				var exists = this.keys.some(function (curKey) {
					return curKey == key;
				});
	
				if (!exists) {
					this.keys.push(key);
				}
			},
	
			_unRegisterKey: function (key) {
				var index = this.keys.indexOf(key);
				this.keys.splice(index, 1);
			},
	
			_isHotKeyPressed: function () {
				var currentHotKey,
					found = false,
					_this = this,
					pressedKeys = this.keys.sort();
	
				for (var hotKey in hotKeys) {
					currentHotKey = hotKeys[hotKey];
					if (pressedKeys.length != currentHotKey.length) continue;
	
					if (currentHotKey.every(function (key, i) { return key == pressedKeys[i]})) {
						_this._trigger('hotKey', hotKey);
						found = true;
					}
				}
	
				return found;
			},
	
			_trigger: function (event, args) {
				this.$el.trigger(event, args)
			},
	
			_focusNextCell: function (keyCode, type) {
				type = type || this.cellType;
	
				var date = datepicker.getParsedDate(this._getFocusedDate()),
					y = date.year,
					m = date.month,
					d = date.date;
	
				if (this._isHotKeyPressed()){
					return;
				}
	
				switch(keyCode) {
					case 37: // left
						type == 'day' ? (d -= 1) : '';
						type == 'month' ? (m -= 1) : '';
						type == 'year' ? (y -= 1) : '';
						break;
					case 38: // up
						type == 'day' ? (d -= 7) : '';
						type == 'month' ? (m -= 3) : '';
						type == 'year' ? (y -= 4) : '';
						break;
					case 39: // right
						type == 'day' ? (d += 1) : '';
						type == 'month' ? (m += 1) : '';
						type == 'year' ? (y += 1) : '';
						break;
					case 40: // down
						type == 'day' ? (d += 7) : '';
						type == 'month' ? (m += 3) : '';
						type == 'year' ? (y += 4) : '';
						break;
				}
	
				var nd = new Date(y,m,d);
				if (nd.getTime() < this.minTime) {
					nd = this.minDate;
				} else if (nd.getTime() > this.maxTime) {
					nd = this.maxDate;
				}
	
				this.focused = nd;
			},
	
			_getFocusedDate: function () {
				var focused  = this.focused || this.selectedDates[this.selectedDates.length - 1],
					d = this.parsedDate;
	
				if (!focused) {
					switch (this.view) {
						case 'days':
							focused = new Date(d.year, d.month, new Date().getDate());
							break;
						case 'months':
							focused = new Date(d.year, d.month, 1);
							break;
						case 'years':
							focused = new Date(d.year, 0, 1);
							break;
					}
				}
	
				return focused;
			},
	
			_getCell: function (date, type) {
				type = type || this.cellType;
	
				var d = datepicker.getParsedDate(date),
					selector = '.datepicker--cell[data-year="' + d.year + '"]',
					$cell;
	
				switch (type) {
					case 'month':
						selector = '[data-month="' + d.month + '"]';
						break;
					case 'day':
						selector += '[data-month="' + d.month + '"][data-date="' + d.date + '"]';
						break;
				}
				$cell = this.views[this.currentView].$el.find(selector);
	
				return $cell.length ? $cell : '';
			},
	
			destroy: function () {
				var _this = this;
				_this.$el
					.off('.adp')
					.data('datepicker', '');
	
				_this.selectedDates = [];
				_this.focused = '';
				_this.views = {};
				_this.keys = [];
				_this.minRange = '';
				_this.maxRange = '';
	
				if (_this.opts.inline || !_this.elIsInput) {
					_this.$datepicker.closest('.datepicker-inline').remove();
				} else {
					_this.$datepicker.remove();
				}
			},
	
			_onShowEvent: function () {
				if (!this.visible) {
					this.show();
				}
			},
	
			_onBlur: function () {
				if (!this.inFocus && this.visible) {
					this.hide();
				}
			},
	
			_onMouseDownDatepicker: function (e) {
				this.inFocus = true;
			},
	
			_onMouseUpDatepicker: function (e) {
				this.inFocus = false;
				this.$el.focus()
			},
	
			_onInput: function () {
				var val = this.$el.val();
	
				if (!val) {
					this.clear();
				}
			},
			
			/*_onInputChange: function (e) {
				var date;
				var hasMoment = typeof moment === 'function';
				if (e.firedBy === this) {
					return;
				}
				if (hasMoment) {
					date = moment(opts.altField.value, opts.dateFormat, opts.formatStrict);
					date = (date && date.isValid()) ? date.toDate() : null;
				}
				else {
					date = new Date(Date.parse(opts.field.value));
				}
				if (isDate(date)) {
				  this.setDate(date);
				}
				if (!this._v) {
					this.show();
				}
			},*/
			
			_onResize: function () {
				if (this.visible) {
					this.setPosition();
				}
			},
			
			_onKeyDown: function (e) {
				var code = e.which;
				this._registerKey(code);
	
				// Arrows
				if (code >= 37 && code <= 40) {
					e.preventDefault();
					this._focusNextCell(code);
				}
	
				// Enter
				if (code == 13) {
					if (this.focused) {
						if (this._getCell(this.focused).hasClass('-disabled-')) return;
						if (this.view != this.opts.minView) {
							this.down()
						} else {
							var alreadySelected = this._isSelected(this.focused, this.cellType);
	
							if (!alreadySelected) {
								this.selectDate(this.focused);
							} else if (alreadySelected && this.opts.toggleSelected){
								this.removeDate(this.focused);
							}
						}
					}
					this.hide();
				}
	
				// Esc
				if (code == 27) {
					this.hide();
				}
			},
	
			_onKeyUp: function (e) {
				var code = e.which;
				this._unRegisterKey(code);
			},
	
			_onHotKey: function (e, hotKey) {
				this._handleHotKey(hotKey);
			},
	
			_onMouseEnterCell: function (e) {
				var $cell = $(e.target).closest('.datepicker--cell'),
					date = this._getDateFromCell($cell);
	
				// Prevent from unnecessary rendering and setting new currentDate
				this.silent = true;
	
				if (this.focused) {
					this.focused = ''
				}
	
				$cell.addClass('-focus-');
	
				this.focused = date;
				this.silent = false;
	
				if (this.opts.range && this.selectedDates.length == 1) {
					this.minRange = this.selectedDates[0];
					this.maxRange = '';
					if (datepicker.less(this.minRange, this.focused)) {
						this.maxRange = this.minRange;
						this.minRange = '';
					}
					this.views[this.currentView]._update();
				}
			},
	
			_onMouseLeaveCell: function (e) {
				var $cell = $(e.target).closest('.datepicker--cell');
	
				$cell.removeClass('-focus-');
	
				this.silent = true;
				this.focused = '';
				this.silent = false;
			},
	
			set focused(val) {
				if (!val && this.focused) {
					var $cell = this._getCell(this.focused);
	
					if ($cell.length) {
						$cell.removeClass('-focus-')
					}
				}
				this._focused = val;
				if (this.opts.range && this.selectedDates.length == 1) {
					this.minRange = this.selectedDates[0];
					this.maxRange = '';
					if (datepicker.less(this.minRange, this._focused)) {
						this.maxRange = this.minRange;
						this.minRange = '';
					}
				}
				if (this.silent) return;
				this.date = val;
			},
	
			get focused() {
				return this._focused;
			},
	
			get parsedDate() {
				return datepicker.getParsedDate(this.date);
			},
	
			set date (val) {
				if (!(val instanceof Date)) return;
	
				this.currentDate = val;
	
				if (this.inited && !this.silent) {
					this.views[this.view]._render();
					this.nav._render();
					if (this.visible && this.elIsInput) {
						this.setPosition();
					}
				}
				return val;
			},
	
			get date () {
				return this.currentDate
			},
	
			set view (val) {
				this.viewIndex = this.viewIndexes.indexOf(val);
	
				if (this.viewIndex < 0) {
					return;
				}
	
				this.prevView = this.currentView;
				this.currentView = val;
	
				if (this.inited) {
					if (!this.views[val]) {
						this.views[val] = new Datepicker.Body(this, val, this.opts)
					} else {
						this.views[val]._render();
					}
	
					this.views[this.prevView].hide();
					this.views[val].show();
					this.nav._render();
	
					if (this.opts.onChangeView) {
						this.opts.onChangeView(val)
					}
					if (this.elIsInput && this.visible) this.setPosition();
				}
	
				return val
			},
	
			get view() {
				return this.currentView;
			},
	
			get cellType() {
				return this.view.substring(0, this.view.length - 1)
			},
	
			get minTime() {
				var min = datepicker.getParsedDate(this.minDate);
				return new Date(min.year, min.month, min.date).getTime()
			},
	
			get maxTime() {
				var max = datepicker.getParsedDate(this.maxDate);
				return new Date(max.year, max.month, max.date).getTime()
			},
	
			get curDecade() {
				return datepicker.getDecade(this.date)
			}
		};
	
		//  Utils
		// -------------------------------------------------
	
		datepicker.getDaysCount = function (date) {
			return new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate();
		};
	
		datepicker.getParsedDate = function (date) {
			return {
				year: date.getFullYear(),
				month: date.getMonth(),
				fullMonth: (date.getMonth() + 1) < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1, // One based
				date: date.getDate(),
				fullDate: date.getDate() < 10 ? '0' + date.getDate() : date.getDate(),
				day: date.getDay()
			}
		};
	
		datepicker.getDecade = function (date) {
			var firstYear = Math.floor(date.getFullYear() / 10) * 10;
	
			return [firstYear, firstYear + 9];
		};
	
		datepicker.template = function (str, data) {
			return str.replace(/#\{([\w]+)\}/g, function (source, match) {
				if (data[match] || data[match] === 0) {
					return data[match]
				}
			});
		};
	
		datepicker.isSame = function (date1, date2, type) {
			if (!date1 || !date2) return false;
			var d1 = datepicker.getParsedDate(date1),
				d2 = datepicker.getParsedDate(date2),
				_type = type ? type : 'day',
	
				conditions = {
					day: d1.date == d2.date && d1.month == d2.month && d1.year == d2.year,
					month: d1.month == d2.month && d1.year == d2.year,
					year: d1.year == d2.year
				};
	
			return conditions[_type];
		};
	
		datepicker.less = function (dateCompareTo, date, type) {
			if (!dateCompareTo || !date) return false;
			return date.getTime() < dateCompareTo.getTime();
		};
	
		datepicker.bigger = function (dateCompareTo, date, type) {
			if (!dateCompareTo || !date) return false;
			return date.getTime() > dateCompareTo.getTime();
		};
	
		Datepicker.language = {
			ru: {
				days: ['Воскресенье', 'Понедельник', 'Вторник', 'Среда', 'Четверг', 'Пятница', 'Суббота'],
				daysShort: ['Вос','Пон','Вто','Сре','Чет','Пят','Суб'],
				daysMin: ['Вс','Пн','Вт','Ср','Чт','Пт','Сб'],
				months: ['Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь', 'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь'],
				monthsShort: ['Янв', 'Фев', 'Мар', 'Апр', 'Май', 'Июн', 'Июл', 'Авг', 'Сен', 'Окт', 'Ноя', 'Дек'],
				today: 'Сегодня',
				clear: 'Очистить',
				dateFormat: 'dd.mm.yyyy',
				firstDay: 1
			}
		};
	
		$.fn[pluginName] = function ( options ) {
			return this.each(function () {
				if (!$.data(this, pluginName)) {
					$.data(this,  pluginName,
						new Datepicker( this, options ));
				} else {
					var _this = $.data(this, pluginName);
	
					_this.opts = $.extend(true, _this.opts, options);
					_this.update();
				}
			});
		};
	
		$(function () {
			$(autoInitSelector).datepicker();
		})
	
	})();
	
	;(function () {
		var templates = {
			days:'' +
			'<div class="datepicker--days datepicker--body">' +
			'<div class="datepicker--days-names"></div>' +
			'<div class="datepicker--cells datepicker--cells-days"></div>' +
			'</div>',
			months: '' +
			'<div class="datepicker--months datepicker--body">' +
			'<div class="datepicker--cells datepicker--cells-months"></div>' +
			'</div>',
			years: '' +
			'<div class="datepicker--years datepicker--body">' +
			'<div class="datepicker--cells datepicker--cells-years"></div>' +
			'</div>'
			},
			D = Datepicker;
	
		D.Body = function (d, type, opts) {
			this.d = d;
			this.type = type;
			this.opts = opts;
	
			this.init();
		};
	
		D.Body.prototype = {
			init: function () {
				this._buildBaseHtml();
				this._render();
	
				this._bindEvents();
			},
	
			_bindEvents: function () {
				this.$el.on('click', '.datepicker--cell', $.proxy(this._onClickCell, this));
			},
	
			_buildBaseHtml: function () {
				this.$el = $(templates[this.type]).appendTo(this.d.$content);
				this.$names = $('.datepicker--days-names', this.$el);
				this.$cells = $('.datepicker--cells', this.$el);
			},
	
			_getDayNamesHtml: function (firstDay, curDay, html, i) {
				curDay = curDay != undefined ? curDay : firstDay;
				html = html ? html : '';
				i = i != undefined ? i : 0;
	
				if (i > 7) return html;
				if (curDay == 7) return this._getDayNamesHtml(firstDay, 0, html, ++i);
	
				html += '<div class="datepicker--day-name' + (this.d.isWeekend(curDay) ? " -weekend-" : "") + '">' + this.d.loc.daysMin[curDay] + '</div>';
	
				return this._getDayNamesHtml(firstDay, ++curDay, html, ++i);
			},
	
			_getCellContents: function (date, type) {
				var classes = "datepicker--cell datepicker--cell-" + type,
					currentDate = new Date(),
					parent = this.d,
					opts = parent.opts,
					d = D.getParsedDate(date),
					render = {},
					html = d.date;
	
				if (opts.onRenderCell) {
					render = opts.onRenderCell(date, type) || {};
					html = render.html ? render.html : html;
					classes += render.classes ? ' ' + render.classes : '';
				}
	
				switch (type) {
					case 'day':
						if (parent.isWeekend(d.day)) classes += " -weekend-";
						if (d.month != this.d.parsedDate.month) {
							classes += " -other-month-";
							if (!opts.selectOtherMonths) {
								classes += " -disabled-";
							}
							if (!opts.showOtherMonths) html = '';
						}
						break;
					case 'month':
						html = parent.loc[parent.opts.monthsField][d.month];
						break;
					case 'year':
						var decade = parent.curDecade;
						html = d.year;
						if (d.year < decade[0] || d.year > decade[1]) {
							classes += ' -other-decade-';
							if (!opts.selectOtherYears) {
								classes += " -disabled-";
							}
							if (!opts.showOtherYears) html = '';
						}
						break;
				}
	
				if (opts.onRenderCell) {
					render = opts.onRenderCell(date, type) || {};
					html = render.html ? render.html : html;
					classes += render.classes ? ' ' + render.classes : '';
				}
	
				if (opts.range) {
					if (D.isSame(parent.minRange, date, type)) classes += ' -range-from-';
					if (D.isSame(parent.maxRange, date, type)) classes += ' -range-to-';
	
					if (parent.selectedDates.length == 1 && parent.focused) {
						if (
							(D.bigger(parent.minRange, date) && D.less(parent.focused, date)) ||
							(D.less(parent.maxRange, date) && D.bigger(parent.focused, date)))
						{
							classes += ' -in-range-'
						}
	
						if (D.less(parent.maxRange, date) && D.isSame(parent.focused, date)) {
							classes += ' -range-from-'
						}
						if (D.bigger(parent.minRange, date) && D.isSame(parent.focused, date)) {
							classes += ' -range-to-'
						}
	
					} else if (parent.selectedDates.length == 2) {
						if (D.bigger(parent.minRange, date) && D.less(parent.maxRange, date)) {
							classes += ' -in-range-'
						}
					}
				}
	
	
				if (D.isSame(currentDate, date, type)) classes += ' -current-';
				if (parent.focused && D.isSame(date, parent.focused, type)) classes += ' -focus-';
				if (parent._isSelected(date, type)) classes += ' -selected-';
				if (!parent._isInRange(date, type) || render.disabled) classes += ' -disabled-';
	
				return {
					html: html,
					classes: classes
				}
			},
	
			/**
			 * Calculates days number to render. Generates days html and returns it.
			 * @param {object} date - Date object
			 * @returns {string}
			 * @private
			 */
			_getDaysHtml: function (date) {
				var totalMonthDays = D.getDaysCount(date),
					firstMonthDay = new Date(date.getFullYear(), date.getMonth(), 1).getDay(),
					lastMonthDay = new Date(date.getFullYear(), date.getMonth(), totalMonthDays).getDay(),
					daysFromPevMonth = firstMonthDay - this.d.loc.firstDay,
					daysFromNextMonth = 6 - lastMonthDay + this.d.loc.firstDay;
	
				daysFromPevMonth = daysFromPevMonth < 0 ? daysFromPevMonth + 7 : daysFromPevMonth;
				daysFromNextMonth = daysFromNextMonth > 6 ? daysFromNextMonth - 7 : daysFromNextMonth;
	
				var startDayIndex = -daysFromPevMonth + 1,
					m, y,
					html = '';
	
				for (var i = startDayIndex, max = totalMonthDays + daysFromNextMonth; i <= max; i++) {
					y = date.getFullYear();
					m = date.getMonth();
	
					html += this._getDayHtml(new Date(y, m, i))
				}
	
				return html;
			},
	
			_getDayHtml: function (date) {
			   var content = this._getCellContents(date, 'day');
	
				return '<div class="' + content.classes + '" ' +
					'data-date="' + date.getDate() + '" ' +
					'data-month="' + date.getMonth() + '" ' +
					'data-year="' + date.getFullYear() + '">' + content.html + '</div>';
			},
	
			/**
			 * Generates months html
			 * @param {object} date - date instance
			 * @returns {string}
			 * @private
			 */
			_getMonthsHtml: function (date) {
				var html = '',
					d = D.getParsedDate(date),
					i = 0;
	
				while(i < 12) {
					html += this._getMonthHtml(new Date(d.year, i));
					i++
				}
	
				return html;
			},
	
			_getMonthHtml: function (date) {
				var content = this._getCellContents(date, 'month');
	
				return '<div class="' + content.classes + '" data-month="' + date.getMonth() + '">' + content.html + '</div>'
			},
	
			_getYearsHtml: function (date) {
				var d = D.getParsedDate(date),
					decade = D.getDecade(date),
					firstYear = decade[0] - 1,
					html = '',
					i = firstYear;
	
				for (i; i <= decade[1] + 1; i++) {
					html += this._getYearHtml(new Date(i , 0));
				}
	
				return html;
			},
	
			_getYearHtml: function (date) {
				var content = this._getCellContents(date, 'year');
	
				return '<div class="' + content.classes + '" data-year="' + date.getFullYear() + '">' + content.html + '</div>'
			},
	
			_renderTypes: {
				days: function () {
					var dayNames = this._getDayNamesHtml(this.d.loc.firstDay),
						days = this._getDaysHtml(this.d.currentDate);
	
					this.$cells.html(days);
					this.$names.html(dayNames)
				},
				months: function () {
					var html = this._getMonthsHtml(this.d.currentDate);
	
					this.$cells.html(html)
				},
				years: function () {
					var html = this._getYearsHtml(this.d.currentDate);
	
					this.$cells.html(html)
				}
			},
	
			_render: function () {
				this._renderTypes[this.type].bind(this)();
			},
	
			_update: function () {
				var $cells = $('.datepicker--cell', this.$cells),
					_this = this,
					classes,
					$cell,
					date;
				$cells.each(function (cell, i) {
					$cell = $(this);
					date = _this.d._getDateFromCell($(this));
					classes = _this._getCellContents(date, _this.d.cellType);
					$cell.attr('class',classes.classes)
				});
			},
	
			show: function () {
				this.$el.addClass('active');
				this.acitve = true;
			},
	
			hide: function () {
				this.$el.removeClass('active');
				this.active = false;
			},
	
			//  Events
			// -------------------------------------------------
	
			_handleClick: function (el) {
				var date = el.data('date') || 1,
					month = el.data('month') || 0,
					year = el.data('year') || this.d.parsedDate.year;
				// Change view if min view does not reach yet
				if (this.d.view != this.opts.minView) {
					this.d.down(new Date(year, month, date));
					return;
				}
				// Select date if min view is reached
				var selectedDate = new Date(year, month, date),
					alreadySelected = this.d._isSelected(selectedDate, this.d.cellType);
	
				if (!alreadySelected) {
					this.d.selectDate(selectedDate);
				} else if (alreadySelected && this.opts.toggleSelected){
					this.d.removeDate(selectedDate);
				}
	
			},
	
			_onClickCell: function (e) {
				var $el = $(e.target).closest('.datepicker--cell');
	
				if ($el.hasClass('-disabled-')) return;
	
				this._handleClick.bind(this)($el);
			}
		};
	})();
	
	;(function () {
		var template = '' +
			'<div class="datepicker--nav-action" data-action="prev">#{prevHtml}</div>' +
			'<div class="datepicker--nav-title">#{title}</div>' +
			'<div class="datepicker--nav-action" data-action="next">#{nextHtml}</div>',
			buttonsContainerTemplate = '<div class="datepicker--buttons"></div>',
			button = '<span class="datepicker--button" data-action="#{action}">#{label}</span>';
	
		Datepicker.Navigation = function (d, opts) {
			this.d = d;
			this.opts = opts;
	
			this.$buttonsContainer = '';
	
			this.init();
		};
	
		Datepicker.Navigation.prototype = {
			init: function () {
				this._buildBaseHtml();
				this._bindEvents();
			},
	
			_bindEvents: function () {
				this.d.$nav.on('click', '.datepicker--nav-action', $.proxy(this._onClickNavButton, this));
				this.d.$nav.on('click', '.datepicker--nav-title', $.proxy(this._onClickNavTitle, this));
				this.d.$datepicker.on('click', '.datepicker--button', $.proxy(this._onClickNavButton, this));
			},
	
			_buildBaseHtml: function () {
				this._render();
				this._addButtonsIfNeed();
			},
	
			_addButtonsIfNeed: function () {
				if (this.opts.todayButton) {
					this._addButton('today')
				}
				if (this.opts.clearButton) {
					this._addButton('clear')
				}
			},
	
			_render: function () {
				var title = this._getTitle(this.d.currentDate),
					html = Datepicker.template(template, $.extend({title: title}, this.opts));
				this.d.$nav.html(html);
				if (this.d.view == 'years') {
					$('.datepicker--nav-title', this.d.$nav).addClass('-disabled-');
				}
				this.setNavStatus();
			},
	
			_getTitle: function (date) {
				return this.d.formatDate(this.opts.navTitles[this.d.view], date)
			},
	
			_addButton: function (type) {
				if (!this.$buttonsContainer.length) {
					this._addButtonsContainer();
				}
	
				var data = {
						action: type,
						label: this.d.loc[type]
					},
					html = Datepicker.template(button, data);
	
				if ($('[data-action=' + type + ']', this.$buttonsContainer).length) return;
				this.$buttonsContainer.append(html);
			},
	
			_addButtonsContainer: function () {
				this.d.$datepicker.append(buttonsContainerTemplate);
				this.$buttonsContainer = $('.datepicker--buttons', this.d.$datepicker);
			},
	
			setNavStatus: function () {
				if (!(this.opts.minDate || this.opts.maxDate) || !this.opts.disableNavWhenOutOfRange) return;
	
				var date = this.d.parsedDate,
					m = date.month,
					y = date.year,
					d = date.date;
	
				switch (this.d.view) {
					case 'days':
						if (!this.d._isInRange(new Date(y, m-1, d), 'month')) {
							this._disableNav('prev')
						}
						if (!this.d._isInRange(new Date(y, m+1, d), 'month')) {
							this._disableNav('next')
						}
						break;
					case 'months':
						if (!this.d._isInRange(new Date(y-1, m, d), 'year')) {
							this._disableNav('prev')
						}
						if (!this.d._isInRange(new Date(y+1, m, d), 'year')) {
							this._disableNav('next')
						}
						break;
					case 'years':
						if (!this.d._isInRange(new Date(y-10, m, d), 'year')) {
							this._disableNav('prev')
						}
						if (!this.d._isInRange(new Date(y+10, m, d), 'year')) {
							this._disableNav('next')
						}
						break;
				}
			},
	
			_disableNav: function (nav) {
				$('[data-action="' + nav + '"]', this.d.$nav).addClass('-disabled-')
			},
	
			_activateNav: function (nav) {
				$('[data-action="' + nav + '"]', this.d.$nav).removeClass('-disabled-')
			},
	
			_onClickNavButton: function (e) {
				var $el = $(e.target).closest('[data-action]'),
					action = $el.data('action');
				this.d[action]();
			},
	
			_onClickNavTitle: function (e) {
				if ($(e.target).hasClass('-disabled-')) return;
				if (this.d.view == 'days') {
					return this.d.view = 'months'
				}
				this.d.view = 'years';
			}
		}
	})();
})(window, jQuery);